import ctypes
import datetime
import inspect
import os
import queue
import re
import threading
import wx.lib.newevent
import serial
import wx
from serial.tools.list_ports_windows import comports
from PowerUDUI import PowerUDPanel
from threading import Semaphore

# 自定义事件
(PostDataEvent, EVT_DATA) = wx.lib.newevent.NewEvent()


class PowerUDFrame(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__(self, parent=None, id=-1, title='autoPowerUD_ver_1.0.0', pos=(-1, -1),
                          size=(-1, -1), style=wx.DEFAULT_FRAME_STYLE, name='PowerOn_frame')
        self.comport = None                                   # 串口
        self.comportChoiceHandle = None                       # 串口选择线程
        self.comRecvHandle = None                             # 串口接收数据线程
        self.listenerHandle = None
        self.runHandle = None                                 # 寻呼、测试线程
        self.sendHandle = None                                # 发送线程
        self.recvString = ''                                  # 存放接收到的数据
        self.condition = threading.Condition()                # 同步线程，接收数据和处理
        self.cndtcomm = threading.Condition()                 # 同步线程，处理数据与判断数据
        self.dataQueue = queue.Queue()                        # 队列 用于存放接收到的数据
        self.semaTest = Semaphore(value=1)                    # 用于同步测试线程，对32颗MCU依次的进行寻呼、测试等
        self.doRecv = False                                   # 串口接收标志
        self.doGettingData = True                             # 获取数据标志

        self.isWirelessFlag = True                            # 无线传输标志
        self.stopFlag = False                                 # 停止线程标志
        self.resetFlag = False                                # 复位线程标志
        self.Bind(EVT_DATA, self.ReceiveProcess)              # 绑定事件，串口接收到数据，将其保存至队列
        self.timeout = 0                                      # 超时时间
        self.chips_bitmap = [                                 # 图标
            wx.Bitmap("./res/chip.png", wx.BITMAP_TYPE_PNG),                # 空
            wx.Bitmap("./res/chip_OK.png", wx.BITMAP_TYPE_PNG),             # 绿色 OK
            wx.Bitmap("./res/chip_ERROR.png", wx.BITMAP_TYPE_PNG),          # 红色 ERROR
            wx.Bitmap("./res/chip_TESTING.png", wx.BITMAP_TYPE_PNG),        # 蓝色 TESTING
            wx.Bitmap("./res/chip_PAGING.png", wx.BITMAP_TYPE_PNG),         # 白色 PAGING
            wx.Bitmap("./res/chip_TESTING_ERROR.png", wx.BITMAP_TYPE_PNG),  # 蓝 红 TESTING_ERROR
        ]
        self.grid_32 = None                                                 # MCU图标存放的布局
        self.results = [0 for i in range(32)]  # 存放寻呼信息 0:None, 1:paging -1:paging error, 2:test ok, -2:test error
        self.initFrame()  # 界面初始化

    def __del__(self):
        pass

    def onClose(self, event):
        """
        退出时关闭正在运行的死循环线程
        :param event: close frame
        :return: None
        """
		# """退出时关闭正在运行的死循环线程"""
        self.doRecv = False
        self.doGettingData = False
        self.isWirelessFlag = True
        if self.runHandle:
            self.runHandle = None
        if self.comRecvHandle:
            self.comRecvHandle = None
        if self.comportChoiceHandle:
            self.comportChoiceHandle = None
        if self.sendHandle:
            self.sendHandle = None
        if self.listenerHandle is not None:
            self.listenerHandle = None
        self.Destroy()

    def Destroy(self):
        super().Destroy()

    def initFrame(self):
        # 创建状态栏
        self.statusbar = self.CreateStatusBar()
        # 将状态栏分割为4部分
        self.statusbar.SetFieldsCount(4)
        # 比例为1:1:1:1
        self.statusbar.SetStatusWidths([-1, -1, -1, -1])

        # 创建主Panel 填充 居中
        self.createMainWindow()
        self.Fit()
        self.Centre(wx.BOTH)

    def createMainWindow(self):
        # 获取串口
        self.getSerialPort()
        # 找到panel
        self.powerUDPanel = PowerUDPanel(self)
        # 添加串口
        self.powerUDPanel.choice_com.SetItems(self.comport)
        self.powerUDPanel.choice_com.SetSelection(0)

        # 初始化控件状态
        self.powerUDPanel.bt_paging.Disable()        # 关闭寻呼按钮
        self.powerUDPanel.tc_minvalue.Disable()      # 关闭最小值
        self.powerUDPanel.tc_step.Disable()          # 关闭步长
        self.powerUDPanel.cb_fastpoweron.Disable()   # 关闭快速上下电
        self.powerUDPanel.rb_HV.Disable()            # 高电平
        self.powerUDPanel.rb_LV.Disable()            # 低电平
        self.powerUDPanel.rb_RiseT.Disable()         # 上升沿选项
        self.powerUDPanel.rb_DropT.Disable()         # 下降沿选项
        self.powerUDPanel.bt_fastTest.Disable()      # 快速测试按钮
        self.powerUDPanel.bt_testAll.Disable()       # 全部测试按钮
        self.powerUDPanel.bt_stop.Disable()          # 停止按钮
        self.powerUDPanel.bt_reset.Disable()         # 复位按钮
 
        # 串口选择添加事件
        self.powerUDPanel.choice_com.Bind(wx.EVT_CHOICE, self.comportChoiceEvt, id=-1)      # 串口选择事件
        self.powerUDPanel.bt_openSerlport.Bind(wx.EVT_BUTTON, self.openComportEvt)          # 打开串口
        self.powerUDPanel.cb_isWireless.Bind(wx.EVT_CHECKBOX, self.isWirelessEvt)           # 无线功能选择
        self.powerUDPanel.cb_variableParam.Bind(wx.EVT_CHECKBOX, self.variableParamEvt)     # 可变参数选择
        self.powerUDPanel.listB_testCase.Bind(wx.EVT_LISTBOX_DCLICK, self.dClickListBoxEvt) # 双击列表删粗
        self.powerUDPanel.bt_paging.Bind(wx.EVT_BUTTON, self.pagingEvt)                     # 寻呼
        self.powerUDPanel.bt_fastTest.Bind(wx.EVT_BUTTON, self.fastTestEvt)                 # fast test
        self.powerUDPanel.bt_testAll.Bind(wx.EVT_BUTTON, self.testAllEvt)                   # test all
        self.powerUDPanel.bt_openLog.Bind(wx.EVT_BUTTON, self.openLogEvt)                   # 打开log
        self.powerUDPanel.bt_clearLog.Bind(wx.EVT_BUTTON, self.clearLogEvt)                 # 清除log
        self.powerUDPanel.bt_addParam.Bind(wx.EVT_BUTTON, self.addParamEvt)                 # 添加测试条件
        self.powerUDPanel.bt_clearParam.Bind(wx.EVT_BUTTON, self.clearParamEvt)             # 删除测试条件
        self.powerUDPanel.bt_reset.Bind(wx.EVT_BUTTON, self.resetEvt)                       # 复位
        self.powerUDPanel.bt_stop.Bind(wx.EVT_BUTTON, self.stopEvt)                         # 停止

        self.powerUDPanel.tc_HVal.SetWindowStyle(wx.TE_PROCESS_ENTER)
        self.powerUDPanel.tc_HVal.Bind(wx.EVT_TEXT_ENTER, self.fastUpdateVh)                # 快速更新测试列表

        self.powerUDPanel.tc_HVal.Bind(wx.EVT_ENTER_WINDOW, self.mouseEnterEvt)             # 鼠标移入提示
        self.powerUDPanel.tc_HVal.Bind(wx.EVT_LEAVE_WINDOW, self.mouseLeaveEvt)             # 鼠标移入提示
        self.Bind(wx.EVT_CLOSE, self.onClose)                                               # 关闭frame

        # 找到MCU图标布局
        self.grid_32 = self.powerUDPanel.GetSizer().GetChildren()[0].GetSizer().GetChildren()[0] \
            .GetSizer().GetChildren()[0].GetSizer().GetChildren()[0].GetSizer()
        self.timeout = int(self.powerUDPanel.sc_timeout.GetValue())  # 获取超时时间

        return self.powerUDPanel

    def initSerialPort(self):
        """初始化串口 获取有那些串口"""
        if self.runHandle is None:
            self.runHandle = threading.Thread(target=self.getSerialPort)
            if self.runHandle.is_alive() is False:
                self.runHandle.start()

    # 事件
    def comportChoiceEvt(self, event):
        """串口变化事件 触发串口切换线程"""
        comStr = event.GetString()  # to get com which be selected
        if self.comportChoiceHandle is None:
            self.comportChoiceHandle = threading.Thread(target=self.comportChoice, args=(comStr,))
            if self.comportChoiceHandle.is_alive() is False:
                self.comportChoiceHandle.start()

    def openComportEvt(self, event):
        """打开串口事件"""
        if self.runHandle is not None:
            wx.MessageBox('you guy has problem！', 'error', wx.ICON_ERROR)
            return
        text = self.powerUDPanel.bt_openSerlport.GetLabelText()
        if self.serl is None:
            self.comportChoice(self.comport[0])
        com = self.serl.port
        if "open serial port" in text and self.serl is not None:
            # serial 开启
            if not self.serl.isOpen():
                self.serl.open()
            self.powerUDPanel.bt_openSerlport.SetLabelText("close serial port")
            self.statusbar.SetStatusText("%s is opened" % com, 0)

            self.doRecv = True
            # 串口接收线程，死循环串口接收数据
            if self.comRecvHandle is None:
                self.comRecvHandle = threading.Thread(target=self.ReceiveData, args=(self,))
                if self.comRecvHandle.is_alive() is False:
                    self.comRecvHandle.start()
			# 死循环处理数据
            if self.listenerHandle is None:
                self.listenerHandle = threading.Thread(target=self.dataRecvListening)
                if self.listenerHandle.is_alive() is False:
                    self.listenerHandle.start()

            if self.isWirelessFlag:  # 无线连接
                self.powerUDPanel.bt_paging.Disable()  # 关闭寻呼按钮
                cmd = "SYSRST" # 复位无线模块
                print(cmd)
                data_encode = [ord(e) for e in cmd]
                self.serl.write(data_encode)  # 串口直接发送数据
            else:  # 有线连接
                self.powerUDPanel.bt_paging.Enable()  # 寻呼使能
            self.powerUDPanel.cb_isWireless.Disable()  # 关闭有线与无线选择
            self.powerUDPanel.choice_com.Disable()  # 关闭串口选择
            self.powerUDPanel.choice_baud.Disable()  # 关闭波特率的选择
        else:
            # serial 关闭
            self.doRecv = False
            self.comRecvHandle = None
            self.powerUDPanel.bt_paging.Disable()
            self.powerUDPanel.cb_isWireless.Enable()
            self.powerUDPanel.choice_com.Enable()
            self.powerUDPanel.choice_baud.Enable()
            self.powerUDPanel.bt_openSerlport.SetLabelText("open serial port")
            self.serl.close()
            self.statusbar.SetStatusText("%s is closed" % com, 0)

    def isWirelessEvt(self, event):
        """选择是否进行无线测试"""
        cb = event.GetEventObject()
        if cb.GetValue():
            self.isWirelessFlag = True  # 无线连接
        else:  # 有线连接
            self.isWirelessFlag = False

    def variableParamEvt(self, event):
        """可变参数选择"""
        cb = event.GetEventObject()
        if cb.GetValue():
            self.powerUDPanel.rb_LV.Enable()         # 低电平
            self.powerUDPanel.tc_minvalue.Enable()   # 最小值
            self.powerUDPanel.tc_step.Enable()       # 步长
        else:
            self.powerUDPanel.rb_LV.Disable()        # 低电平
            self.powerUDPanel.tc_minvalue.Disable()  # 最小值
            self.powerUDPanel.tc_step.Disable()      # 步长

    def dClickListBoxEvt(self, event):
        """双击删除测试项目"""
        listBox = event.GetEventObject()
        select = listBox.GetSelection()
        listBox.Delete(select)

    def mouseEnterEvt(self, e):
        # 鼠标移入提示
        self.statusbar.SetStatusText("press 'Enter' to update the all test case", 3)

    def mouseLeaveEvt(self, e):
        # 鼠标移出提示
        self.statusbar.SetStatusText("", 3)

    def fastUpdateVh(self, event):
        # 快速更新测试列表
        Vh = event.GetEventObject().GetValue()
        listBox = self.powerUDPanel.listB_testCase
        params = listBox.GetItems()
        newTestList = []
        if Vh is not "":
            for param in params:
                oldVh = re.findall(r"Vh\d.*?,", param)[0]
                newTestList.append(param.replace(oldVh, "Vh"+Vh+","))
            self.powerUDPanel.listB_testCase.SetItems([])
            self.powerUDPanel.listB_testCase.SetItems(newTestList)

    def pagingEvt(self):
        """寻呼 事件"""
        if self.runHandle is None:
            self.runHandle = threading.Thread(target=self.paging)
            if self.runHandle.is_alive() is False:
                self.runHandle.start()

    def fastTestEvt(self, event):
        """快速测试 事件"""
        if self.runHandle is None:
            self.runHandle = threading.Thread(target=self.fastTest)
            if self.runHandle.is_alive() is False:
                print("start to fast test...")
                self.runHandle.start()

    def testAllEvt(self, event):
        """多条测试 事件"""
        if self.runHandle is None:
            self.runHandle = threading.Thread(target=self.testAll)
            if self.runHandle.is_alive() is False:
                print("start to test all...")
                self.runHandle.start()

    def stopEvt(self, event):
        """停止测试 事件"""
        print("stop...")
        threading.Thread(target=self.stop).start()

    def resetEvt(self, event):
        """复位 事件"""
        print("reset...")
        if self.runHandle is None:
            self.runHandle = threading.Thread(target=self.reset)
            if self.runHandle.is_alive() is False:
                self.runHandle.start()

    def openLogEvt(self, event):
        """打开测试结果"""
        path = "log/testLog.txt"
        absPath = os.path.abspath(path)
        try:
            os.startfile(absPath)
        except:
            wx.MessageBox('no file！', 'error', wx.ICON_ERROR)

    def clearLogEvt(self, event):
        """删除测试结果 事件"""
        path = "log/testLog.txt"
        absPath = os.path.abspath(path)
        try:
            os.remove(absPath)
            wx.MessageBox('file delete successfully!', 'info', wx.ICON_INFORMATION)
        except PermissionError:
            wx.MessageBox('please close file!', 'info', wx.ICON_INFORMATION)
        except FileNotFoundError:
            wx.MessageBox('file had deleted!', 'info', wx.ICON_INFORMATION)

    def addParamEvt(self, event):
        """添加测试条件 事件"""
        param = self.getParameter()
        if not param:
            return False
        if self.powerUDPanel.cb_variableParam.IsChecked():
            # 高电平 低电平[七时至 低电平 步长] 上升沿 下降沿 周期 循环次数
            cmd = "?[255],[set],[Vh%s,Vl[%s,%s,%s],Tr%s,Td%s,Tms%s,Tn%s]" \
                  % (param[2], param[0], param[3], param[1], param[4], param[5], param[6], param[7])
        else:
            cmd = "?[255],[set],[Vh%s,Vl%s,Tr%s,Td%s,Tms%s,Tn%s]" \
                  % (param[2], param[3], param[4], param[5], param[6], param[7])
        self.powerUDPanel.listB_testCase.Append(cmd)

    def clearParamEvt(self, event):
        answer = wx.MessageBox("clear all items?", "info", wx.ICON_WARNING | wx.YES_NO | wx.NO_DEFAULT)
        if answer != wx.YES:
            return
        self.powerUDPanel.listB_testCase.SetItems([])

    # 接收到串口数据 将其存放入dataQueue队列中
    def ReceiveProcess(self, event):
        """接收数据并存入队列"""
        self.condition.acquire()
        self.dataQueue.put(event.value)
        self.condition.notify()  # 解锁wait
        self.condition.release()  # 解锁data

    # 线程
    def getSerialPort(self):
        """获取串口列表 线程"""
        self.comport = []
        self.coms = list(comports())
        self.coms = serial.tools.list_ports_windows.comports()
        self.serl = None
        if len(self.coms) <= 0:
            print("No coms!!")
        else:
            for com in self.coms:
                self.comport.append(com[0])
        self.runHandle = None
        return self.comport

    def comportChoice(self, com):
        """
        串口切换 线程
        1 切换（打开）串口
        2 测试此串口通信是否正常
        """
        try:
            self.serl = None
            select = self.powerUDPanel.choice_baud.GetSelection()
            baud = self.powerUDPanel.choice_baud.GetString(select)
            self.serl = serial.Serial(
                port=com,
                baudrate=baud,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                bytesize=serial.EIGHTBITS,
                timeout=5)
            self.statusbar.SetStatusText("%s is ready" % com, 0)
            self.comportChoiceHandle = None
            return 0
        except:
            self.statusbar.SetStatusText("%s can not be open" % com, 0)
            self.comportChoiceHandle = None
            return 1

    def dataRecvListening(self):
        """通信 死循环处理下位机数据"""
        print("Start to listening....")
        while self.listenerHandle is not None:
            self.condition.acquire()
            strings = []
            flag = 0
            clientData = ""
            self.doGettingData = True
            while self.doGettingData:  # 死循环读取数据 将数据读空则跳出
                if self.dataQueue.empty():
                    if len(strings) > 0:
                        for s in strings:  # 接收的数据都保存
                            if "!" in s:  # 是否有所要提取的数据
                                clientData = s  # 有则将数据保存 并退出
                                flag = 1
                                break
                            if "CONNECTED" in s:  # 是否有所要提取的数据
                                self.statusbar.SetStatusText("client has connected!", 2)
                                self.powerUDPanel.bt_paging.Enable()
                                flag = 1
                                break
                else:
                    data = self.dataQueue.get()
                    # print(self.byteToStr(data))
                    strings.append(self.byteToStr(data))
                if flag == 1:
                    break
                if self.condition.wait(0.001) is True:
                    pass
            if len(clientData) > 0:  # 如果收到了客户端的数据
                self.recvString = clientData
                self.cndtcomm.acquire()
                self.cndtcomm.notify()  # 收到客户端信息才解锁 取消等待
                self.cndtcomm.release()
                # self.statusbar.SetStatusText(clientData, 2)
                # print(clientData)

            self.condition.release()  # 解锁
        self.doGettingData = False

    def sendData(self, data):
        """
        向串口发送数据
        :param data:待发送数据
        :return: 发送成功Ture或者失败False
        """
        wx.MilliSleep(80)
        self.semaTest.acquire()
        try:
            # 对待发送数据进行编码
            data_encode = [ord(e) for e in data]
            self.serl.write(data_encode)  # 串口直接发送数据
            self.semaTest.release()
            self.sendHandle = None
            return True
        except:
            print("send data error!" + "," + data)
        self.sendHandle = None
        self.semaTest.release()
        return False

    def ReceiveData(self, win):
        """ Receive data from com and return the data """
        if self.serl.is_open:
            self.serl.reset_input_buffer()
        while self.doRecv:
            try:
                data = self.serl.read()
            except:
                wx.MilliSleep(10)
                continue

            while self.serl.is_open and self.serl.in_waiting > 0:
                data = data + self.serl.read(self.serl.in_waiting)
                wx.MilliSleep(10)

            if len(data):
                evt = PostDataEvent(value=data.hex().upper())
                wx.PostEvent(win, evt)
        return

    def findBmp(self, num):
        """根据索引 找到MCU图标"""
        # 将32个MCU图标全部初始化
        grid_32 = self.grid_32
        bmp = None
        if num <= 3:  # 主控0
            grid_4 = grid_32.GetChildren()[6].GetSizer()
            group_2 = grid_4.GetChildren()[num].GetSizer()
            bmp = group_2.GetChildren()[0].GetWindow()
        elif 4 <= num <= 7:  # 主控1
            grid_4 = grid_32.GetChildren()[7].GetSizer()
            group_2 = grid_4.GetChildren()[num - 4].GetSizer()
            bmp = group_2.GetChildren()[0].GetWindow()
        elif 8 <= num <= 11:  # 主控2
            grid_4 = grid_32.GetChildren()[4].GetSizer()
            group_2 = grid_4.GetChildren()[num - 8].GetSizer()
            bmp = group_2.GetChildren()[0].GetWindow()
        elif 12 <= num <= 15:  # 主控3
            grid_4 = grid_32.GetChildren()[5].GetSizer()
            group_2 = grid_4.GetChildren()[num - 12].GetSizer()
            bmp = group_2.GetChildren()[0].GetWindow()
        elif 16 <= num <= 19:  # 主控4
            grid_4 = grid_32.GetChildren()[2].GetSizer()
            group_2 = grid_4.GetChildren()[num - 16].GetSizer()
            bmp = group_2.GetChildren()[0].GetWindow()
        elif 20 <= num <= 23:  # 主控5
            grid_4 = grid_32.GetChildren()[3].GetSizer()
            group_2 = grid_4.GetChildren()[num - 20].GetSizer()
            bmp = group_2.GetChildren()[0].GetWindow()
        elif 24 <= num <= 27:  # 主控6
            grid_4 = grid_32.GetChildren()[0].GetSizer()
            group_2 = grid_4.GetChildren()[num - 24].GetSizer()
            bmp = group_2.GetChildren()[0].GetWindow()
        elif 28 <= num <= 31:  # 主控7
            grid_4 = grid_32.GetChildren()[1].GetSizer()
            group_2 = grid_4.GetChildren()[num - 28].GetSizer()
            bmp = group_2.GetChildren()[0].GetWindow()

        return bmp

    def getParameter(self):
        """从参数栏获取参数并返回"""
        param = []
        # 获取起始参数
        minValue = self.powerUDPanel.tc_minvalue.GetValue()
        param.append(minValue)
        # 获取步长
        step = self.powerUDPanel.tc_step.GetValue()
        param.append(step)
        # 获取高电平
        Vh = self.powerUDPanel.tc_HVal.GetValue()
        param.append(Vh)
        # 获取低电平
        Vl = self.powerUDPanel.tc_LVal.GetValue()
        param.append(Vl)
        # 获取上升沿
        Tr = self.powerUDPanel.tc_riseT.GetValue()
        param.append(Tr)
        # 获取下降沿
        Td = self.powerUDPanel.tc_dropT.GetValue()
        param.append(Td)
        # 获取周期
        Tms = self.powerUDPanel.tc_Tms.GetValue()
        param.append(Tms)
        # 获取循环次数
        Tn = self.powerUDPanel.tc_Tn.GetValue()
        param.append(Tn)

        if int(Vh) < int(Vl):
            wx.MessageBox('please check param[Vh,Vl]!', 'ERROR!', wx.ICON_WARNING)
            return False
        if int(minValue) > int(Vl):
            wx.MessageBox('please check param[minValue,Vl]!', 'ERROR!', wx.ICON_WARNING)
            return False
        return param

    def paging(self):
        """
        寻呼
        :return:None
        """
        # 获取起始寻呼数据
        pagStart = int(self.powerUDPanel.sc_paging1.GetValue())
        pagEnd = int(self.powerUDPanel.sc_paging2.GetValue()) + 1
        # 检查寻呼地址是否正确
        if pagStart >= pagEnd:
            wx.MessageBox('please check the paging address!', 'ERROR!', wx.ICON_WARNING)
            self.runHandle = None
            return False

        # 从start到End挨个产生线程进行寻呼
        for num in range(pagStart, pagEnd):

            cmd = '?[%s],[paging]\r\n\0' % (str(num))
            cmd += chr(self.CRC8(cmd))  # num -> ascii
            self.powerUDPanel.gauge.SetValue(int(num / 31 * 100))
            self.powerUDPanel.st_percent.SetLabelText(str(int(num / 31 * 100)) + "%")
            print(cmd)
            if self.sendHandle is None:
                self.sendHandle = threading.Thread(target=self.sendData, args=(cmd,))
                if self.sendHandle.is_alive() is False:
                    self.sendHandle.start()

            bmp = self.findBmp(num)
            ack = "paging"
            if self.JudgeRecvData(ack) == "OK":
                bmp.SetBitmap(self.chips_bitmap[4])  # PAGING
                self.results[num] = 1
                self.statusbar.SetStatusText("paging:MCU" + str(num) + ",OK", 2)
            else:
                bmp.SetBitmap(self.chips_bitmap[0])  # None
                self.results[num] = -1
                self.statusbar.SetStatusText("paging:MCU" + str(num) + ",ERROR", 2)

        self.powerUDPanel.bt_fastTest.Enable()
        self.powerUDPanel.bt_testAll.Enable()
        self.statusbar.SetStatusText("paging:completed", 2)
        self.powerUDPanel.gauge.SetValue(0)
        self.powerUDPanel.st_percent.SetLabelText(str(0) + "%")
        self.runHandle = None

    def fastTest(self):
        """一键测试"""
        # 获取起始测试数据
        pagStart = int(self.powerUDPanel.sc_paging1.GetValue())
        pagEnd = int(self.powerUDPanel.sc_paging2.GetValue()) + 1
        # 检查寻呼地址是否正确
        if pagStart >= pagEnd:
            wx.MessageBox('please check the paging address!', 'ERROR!', wx.ICON_WARNING)
            self.runHandle = None
            return False
        # 使能 停止
        self.powerUDPanel.bt_stop.Enable()

        # 获取参数 param[minvalue, step, Vh, Vl, Tr, Td, Tms, Tn]
        param = self.getParameter()

        if self.powerUDPanel.cb_variableParam.IsChecked():
            cmd = "?[255],[set],[Vh%s,Vl[%s,%s,%s],Tr%s,Td%s,Tms%s,Tn%s]" \
                  % (param[2], param[0], param[3], param[1], param[4], param[5], param[6], param[7])
        else:
            cmd = "?[255],[set],[Vh%s,Vl%s,Tr%s,Td%s,Tms%s,Tn%s]" \
                  % (param[2], param[3], param[4], param[5], param[6], param[7])
        cmd += chr(self.CRC8(cmd))  # num -> ascii
        #  群发 设置参数
        if self.sendHandle is None:
            self.sendHandle = threading.Thread(target=self.sendData, args=(cmd,))
            if self.sendHandle.is_alive() is False:
                self.sendHandle.start()
        if self.JudgeRecvData("None") == "OK":  # 设置参数 无回复
            # 询问是否设置成功
            cmd = "repeat"
            self.askClient(pagStart, pagEnd, cmd)

            for i in range(int(self.powerUDPanel.tc_Tn.GetValue())):
                wx.MilliSleep(int(self.powerUDPanel.tc_Tms.GetValue()))
                self.statusbar.SetStatusText("please wait " + str(int(self.powerUDPanel.tc_Tn.GetValue()) - i) + "s", 3)
            # 询问 结果
            self.statusbar.SetStatusText("", 3)
            cmd = "report"
            self.askClient(pagStart, pagEnd, cmd)
        else:
            self.runHandle = None
            return False
        #  禁止停止
        self.powerUDPanel.bt_stop.Disable()
        self.runHandle = None

    def testAll(self):
        """多条测试"""
        # 获取起始测试地址
        pagStart = int(self.powerUDPanel.sc_paging1.GetValue())
        pagEnd = int(self.powerUDPanel.sc_paging2.GetValue()) + 1
        # 检查寻呼地址是否正确
        if pagStart >= pagEnd:
            wx.MessageBox('please check the paging address!', 'ERROR!', wx.ICON_WARNING)
            self.runHandle = None
            return False
        # 获取参数列表 param[minvalue, step, Vh, Vl, Tr, Td, Tms, Tn]
        params = self.powerUDPanel.listB_testCase.GetItems()
        if len(params) == 0:
            wx.MessageBox('please check the parameter list!', 'ERROR!', wx.ICON_WARNING)
            self.runHandle = None
            return False
        # 使能 停止
        self.powerUDPanel.bt_stop.Enable()
        # 去除 测试结果
        for i in range(32):
            if self.results[i] == -2 or self.results[i] == 2:
                self.results[i] = 1  # error or ok ---> paging

        for param in params:
            self.statusbar.SetStatusText("testing " + str(params.index(param) + 1), 1)
            # 清空 test log
            logs = []
            # 群发设置测试参数
            # 添加结束符
            param += "\r\n\0"
            # 添加校验
            param += chr(self.CRC8(param))  # num -> ascii
            if self.sendHandle is None:
                self.sendHandle = threading.Thread(target=self.sendData, args=(param,))
                if self.sendHandle.is_alive() is False:
                    self.sendHandle.start()
            if self.JudgeRecvData("None") == "OK":  # 设置参数无回复，传入参数None让后续线程解锁
                # 询问是否设置成功
                cmd = "repeat"
                self.askClient(pagStart, pagEnd, cmd)
                # 循环询问测试结果
                for n in range(pagStart, pagEnd):
                    # 如果不在寻呼列表 则跳过收报告和得结果
                    if self.results[n] == -1 or self.results[n] == 0:
                        logs.append("MCU" + str(n) + ":no paging")  # 保存数据
                        continue
                    # 找到当前芯片图标位置
                    bmp = self.findBmp(n)
                    # 死循环询问 直到结果为OK或者ERROR
                    repeat_count = 20  # 数据出错或者连接失败的重复次数
                    while True:
                        if repeat_count == 0:  # 超过重试次数则跳过
                            break
                        cmd = "report_multiply"
                        reply = self.askClient(n, n + 1, cmd)
                        if "[OK]" in reply:  # 收到报告 ，无错误
                            if self.results[n] == -2 or self.results[n] == 3:  # 这次有错误或者之前有错误
                                self.results[n] = 3  # 这次正确 但是之前出过错
                                bmp.SetBitmap(self.chips_bitmap[2])  # ERROR
                            else:
                                self.results[n] = 2  # 0: None, 1:paging, -1:no paging, 2:ok, -2 error
                                bmp.SetBitmap(self.chips_bitmap[1])  # OK
                            logs.append("MCU" + str(n) + ":OK")  # 保存数据
                            self.statusbar.SetStatusText(re.findall(r"!\[.*\]", reply)[0], 3)
                            break
                        elif "[ERROR," in reply:  # 收到报告 ，有错误
                            bmp.SetBitmap(self.chips_bitmap[2])  # ERROR
                            self.results[n] = -2  # 0: None, 1:paging, -1:no paging, 2:ok, -2 error
                            try:
                                log = re.findall(r"ERROR,.*?]", reply)[0]
                                logs.append("MCU" + str(n) + ":" + log)  # 保存数据
                                self.statusbar.SetStatusText(re.findall(r"!\[.*\]", reply)[0], 3)
                                break
                            except:
                                continue
                        elif "[report],[BUSY," in reply:  # 无报告，得到进度
                            if self.isWirelessFlag:
                                try:
                                    bar = int(re.findall(r"\b\d+\b", reply)[1])
                                    print("progress:" + str(bar))
                                except:
                                    print("get bar error!")
                                    continue

                            self.powerUDPanel.gauge.SetValue(bar)
                            self.powerUDPanel.st_percent.SetLabelText(str(bar) + "%")
                            Vl = int(1200 * bar / 100)
                            if Vl % 10 >= 5:  # 五入
                                Vl = (int(Vl / 10) + 1) * 10
                            else:
                                Vl = int(Vl / 10) * 10
                            self.statusbar.SetStatusText("current Vl: " + str(Vl) + "mV", 3)
                        elif "ERROR" in reply:
                            # bmp.SetBitmap(self.chips_bitmap[2])      # ERROR
                            print("ACK ERROR:" + reply)
                            repeat_count = repeat_count - 1
                            if self.isWirelessFlag:  # 如果是无线连接
                                for i in range(5):
                                    self.statusbar.SetStatusText(
                                        "ack error, count:" + str(repeat_count) + ", retry after waiting " + str(
                                            5 - i) + "s...", 3)
                                    wx.MilliSleep(1000)
                            else:
                                wx.MilliSleep(2000)
                            continue
                        elif "PASS" in reply:  # 没有收到期待的消息
                            repeat_count = repeat_count - 1
                            print("chip" + str(n) + ",timeout")
                            logs.append("chip" + str(n) + ",timeout")  # 保存数据
                            self.statusbar.SetStatusText("timeout ,PASS", 3)
                            break
                        else:
                            # wx.MessageBox('recv error!', 'info', wx.ICON_ERROR)
                            print("recv error")
                            repeat_count = repeat_count - 1
                            continue
                        wx.MilliSleep(self.timeout * 4)  # 等待 (timeout * 4) ms
            else:
                self.runHandle = None
                return False
            self.saveLog(logs, param)  # 每测完一条case 则保存测试数据
        self.statusbar.SetStatusText("test completed ☺", 2)
        self.statusbar.SetStatusText("★★★★★", 3)
        # 禁止 停止和复位按钮
        self.powerUDPanel.bt_stop.Disable()
        self.powerUDPanel.bt_reset.Disable()
        self.runHandle = None

    def reset(self):
        cmd = "?[255],[reset]\r\n\0"
        cmd += chr(self.CRC8(cmd))
        self.sendData(cmd)
        self.runHandle = None

    def stop(self):
        if self.runHandle is None:
            wx.MessageBox('nothing can stop!', 'WARNING!', wx.ICON_WARNING)
            return
        else:
            print("stop thread...")
            self.stop_thread(self.runHandle)
            wx.MessageBox('test had stopped!please restart the software!', 'INFO', wx.ICON_INFORMATION)
            cmd = "?[255],[stop]\r\n\0"
            cmd += chr(self.CRC8(cmd))
            self.sendData(cmd)
            self.powerUDPanel.gauge.SetValue(0)
            self.powerUDPanel.st_percent.SetLabelText(str(0) + "%")
            self.runHandle = None

    def _async_raise(self, tid, exctype):
        """raises the exception, performs cleanup if needed"""
        tid = ctypes.c_long(tid)
        if not inspect.isclass(exctype):
            exctype = type(exctype)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("invalid thread id")  # 无效的 id
        elif res != 1:
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    def stop_thread(self, thread):
        self._async_raise(thread.ident, SystemExit)

    # 函数
    def byteToStr(self, data):
        tmp = "%s" % (re.sub("(?<=\w\w)(?=(?:\w\w)+$)", " ", data))
        s = ''
        for item in tmp.split():
            s += chr(int(item, 16))
        return s

    # CRC-8算法
    def CRC8(self, buffer):
        length = len(buffer)
        temp = 0
        for i in range(0, length):
            temp ^= ord(buffer[i])  # ascii -> num
            for j in range(0, 8):
                if temp & 0x80:
                    temp = (temp << 1) ^ 0x31
                else:
                    temp <<= 1
        return temp & 0xFF

    def JudgeRecvData(self, ack):
        """
        判断接收的内容是否符合预期
        :param ack: 期待值
        :return: 符合或者不符合
        """
        self.cndtcomm.acquire()
        if self.cndtcomm.wait(self.timeout / 2000) is True:  # 等待1s 接收客户端信息
            pass
        if (ack in self.recvString and "!" in self.recvString) or ack == "None":
            print("ACK:" + self.recvString)
            print("******************")
            print(ack)
            print("******************")
            self.recvString = ''
            self.cndtcomm.release()
            return "OK"
        elif "report_multiply" == ack and "!" in self.recvString:
            reply = self.recvString
            self.recvString = ''
            self.cndtcomm.release()
            return reply
        self.cndtcomm.release()
        return "ERROR"

    def askClient(self, start, end, r):
        """
        :param start:询问起始地址
        :param end: 询问结束地址
        :param r: 指令类型
        :return:
        """
        result = []
        for n in range(start, end):
            if "repeat" in r:
                self.statusbar.SetStatusText("repeating," + str(n), 2)
            elif "report" in r:
                self.statusbar.SetStatusText("reporting," + str(n), 2)
            # 如果没有寻呼到则跳过
            if self.results[n] == -1 or self.results[n] == 0:
                result.append("MCU" + str(n) + ":no paging")
                continue
            if r == "repeat":
                cmd = '?[%s],[repeat]\r\n\0' % (str(n))
            else:
                cmd = '?[%s],[report]\r\n\0' % (str(n))
            cmd += chr(self.CRC8(cmd))  # num -> ascii

            if self.sendHandle is None:
                self.sendHandle = threading.Thread(target=self.sendData, args=(cmd,))
                if self.sendHandle.is_alive() is False:
                    self.sendHandle.start()
            bmp = self.findBmp(n)
            if r == "repeat":
                if self.JudgeRecvData("[OK]") == "OK":
                    if self.results[n] == -2 or self.results[n] == 3:  # 如果该芯片之前已经出错则改为TESTING_ERROR
                        bmp.SetBitmap(self.chips_bitmap[5])  # TESTING_ERROR   正在测试中 但是有错
                    else:
                        bmp.SetBitmap(self.chips_bitmap[3])  # TESTING
                else:
                    print("repeat error!")
                    bmp.SetBitmap(self.chips_bitmap[2])  # repeat ERROR
                if not self.isWirelessFlag:
                    wx.MilliSleep(200)
                else:
                    wx.MilliSleep(100)
            elif r == "report":
                if self.JudgeRecvData("[OK]") == "OK":
                    result.append("MCU" + str(n) + ":OK")
                    bmp.SetBitmap(self.chips_bitmap[1])  # OK
                else:
                    result.append("MCU" + str(n) + ":ERROR")
                    bmp.SetBitmap(self.chips_bitmap[2])  # ERROR
            elif r == "report_multiply":
                return self.JudgeRecvData(r)
            else:
                bmp.SetBitmap(self.chips_bitmap[0])  # None
        if r == "report":
            param = self.getParameter()
            if self.powerUDPanel.cb_variableParam.IsChecked():
                p = "?[255],[set],[Vh%s,Vl[%s,%s,%s],Tr%s,Td%s,Tms%s,Tn%s]" \
                    % (param[2], param[0], param[3], param[1], param[4], param[5], param[6], param[7])
            else:
                p = "?[255],[set],[Vh%s,Vl%s,Tr%s,Td%s,Tms%s,Tn%s]" \
                    % (param[2], param[3], param[4], param[5], param[6], param[7])
            self.saveLog(result, p)
        self.statusbar.SetStatusText(r + " completed!", 2)
        return "PASS"

    def saveLog(self, logs, param, savePath='log/testLog.txt'):
        with open(savePath, 'a+', encoding='utf-8') as file:
            curr_time = datetime.datetime.now()
            time = datetime.datetime.strftime(curr_time, '%Y-%m-%d %H:%M:%S')
            file.writelines("time:" + time + '\n')
            file.writelines("condition:" + re.findall(r"Vh.*?Tn\d", param)[0] + '\n')
            count = 0
            for log in logs:
                count = count + 1
                file.writelines(log + '\n')
                if count == 4:  # 每4条记录 打一个空行
                    count = 0
                    file.writelines('\n')
            file.writelines('\n\n')
        file.close()


def Main():
    app = wx.App()
    powerUDFrame = PowerUDFrame(None)
    powerUDFrame.Show()
    app.MainLoop()
    app.Destroy()


if __name__ == '__main__':
    Main()

# #############################################################
# BUG:
# 1.点击停止按钮后，会造成死锁问题
#     点击停止后，再次发送数据 cndtcomm这个锁解不开，导致收不到数据
# 2.无线测试方案时， 寻呼经常失败
#     在无线监听时，间隔时间太长，原来为0.1，改为0.05后 寻呼正常
# 3.测试时发现LOG显示正确，而UI却显示该芯片为全红
#     以log结果为准，UI显示红色只是repeat时，通信异常了
# ############################################################
